package com.boot2.core.utils;

import lombok.SneakyThrows;
import lombok.extern.apachecommons.CommonsLog;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.URI;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zhangweilin
 * @description: http 工具，单例支持
 * @date 2018/10/25
 */
@CommonsLog
public abstract class AbstractHttpsClientInstance {

    private static final String tokenKey = "tokenKey";
    private static final String tokenValue = "tokenValue";
    private static final String userName = "userName";
    //    public static String token;
    private static final String password = "password";
    //如果为了调试,可以临时修改超时时间
    public static Long connectTimeout = 60 * 2L;
    public static Long readTimeout = 60 * 2L;
    public static Long writeTimeout = 60 * 2L;
    /**
     * 使用volatile双重校验锁
     **/
    private static volatile Semaphore semaphore = null;
    //非代理连接实例
    private static volatile OkHttpClient okHttpClient = null;
    private static volatile OkHttpClient okHttpsClient = null;
    //代理连接实例
    private static volatile OkHttpClient okHttpClientProxy = null;
    private static volatile OkHttpClient okHttpsClientProxy = null;
    /**
     * 存储一些基本参数,里面是一个map结构，如:
     * key:        value              说明
     * tokenKey    x-access-token     传的token本身的变量名，如 -access-token
     * tokenValue  xxxxxxxxxxxxxx     token的值,一般为登录返回的token值
     * userName    zhangweilin        用户名
     * password    123456             密码
     */
    private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();

    /**
     * 建立单例模式
     */
    public static Semaphore getSemaphoreInstance() {
        // 只能0个线程同时访问
        synchronized (HttpsClient.class) {
            if (semaphore == null) {
                semaphore = new Semaphore(0);
            }
        }
        return semaphore;
    }

    /**
     * 建立单例模式
     */
    protected static OkHttpClient getHttpInstance() {
        synchronized (HttpsClient.class) {
            if (okHttpClient == null) {
                // 这里是以毫秒为单位，1000 毫秒 = 1秒
                // header里加token
                OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
                String tokenKey = getTokenKey();
                String tokenValue = (String) getTokenValue();
                if (StringUtils.isNotEmpty(tokenKey) && StringUtils.isNotEmpty(tokenValue)) {
                    builder = builder.addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request request = chain.request().newBuilder().addHeader(tokenKey, tokenValue).build();
                            return chain.proceed(request);
                        }
                    });
                }
                //base auth
                String userName = getUserName();
                String password = getPassword();
                if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
                    builder.authenticator((route, response) -> {
                        String credential = Credentials.basic(userName, password);
                        return response.request().newBuilder().header("Authorization", credential).build();
                    });
                }

                okHttpClient = builder.connectTimeout(connectTimeout, TimeUnit.SECONDS)// 设置超时时间
                        .readTimeout(readTimeout, TimeUnit.SECONDS)// 设置读取超时时间
                        .writeTimeout(writeTimeout, TimeUnit.SECONDS)// 设置写入超时时间
                        .build();
            }
        }
        return okHttpClient;
    }

    /**
     * 建立单例模式
     */
    protected static OkHttpClient getProxyHttpInstance(String proxyHost, int proxyPort) {
        synchronized (HttpsClient.class) {
            if (okHttpClientProxy == null) {
                // 这里是以毫秒为单位，1000 毫秒 = 1秒
                OkHttpClient.Builder builder = new OkHttpClient().newBuilder().proxySelector(new ProxySelector() {
                    @Override
                    public List<Proxy> select(URI uri) {
                        log.info("获取代理时,uri: " + uri);
                        List<Proxy> proxyList = new ArrayList<>(1);
                        proxyList.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
                        return proxyList;
                    }

                    @Override
                    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                        log.info("获取代理失败时,uri: " + uri);
                        log.info("获取代理失败时,sa: " + sa);
                        log.info("获取代理失败时,ioe: " + ioe);
                    }
                });
                String tokenKey = getTokenKey();
                String tokenValue = (String) getTokenValue();
                if (StringUtils.isNotEmpty(tokenKey) && StringUtils.isNotEmpty(tokenValue)) {
                    builder = builder.addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request request = chain.request().newBuilder().addHeader(tokenKey, tokenValue).build();
                            return chain.proceed(request);
                        }
                    });
                }

                //base auth
                String userName = getUserName();
                String password = getPassword();
                if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
                    builder.authenticator((route, response) -> {
                        String credential = Credentials.basic(userName, password);
                        return response.request().newBuilder().header("Authorization", credential).build();
                    });
                }
                okHttpClientProxy = builder.connectTimeout(connectTimeout, TimeUnit.SECONDS)// 设置超时时间
                        .readTimeout(readTimeout, TimeUnit.SECONDS)// 设置读取超时时间
                        .writeTimeout(writeTimeout, TimeUnit.SECONDS)// 设置写入超时时间
                        .build();
            }
        }
        return okHttpClient;
    }

    /**
     * 建立单例模式
     */
    protected static OkHttpClient getHttpsInstance() {
        synchronized (HttpsClient.class) {
            if (okHttpsClient == null) {
                // 这里是以毫秒为单位，1000 毫秒 = 1秒
                OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
                String tokenKey = getTokenKey();
                String tokenValue = (String) getTokenValue();
                if (StringUtils.isNotEmpty(tokenKey) && StringUtils.isNotEmpty(tokenValue)) {
                    builder = builder.addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request request = chain.request().newBuilder().addHeader(tokenKey, tokenValue).build();
                            return chain.proceed(request);
                        }
                    });
                }

                //base auth
                String userName = getUserName();
                String password = getPassword();
                if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
                    builder.authenticator((route, response) -> {
                        String credential = Credentials.basic(userName, password);
                        return response.request().newBuilder().header("Authorization", credential).build();
                    });
                }

                okHttpsClient = builder.sslSocketFactory(createSSLSocketFactory(),createX509TrustManager()).hostnameVerifier(new HttpsClient.TrustAllHostnameVerifier()).connectTimeout(60 * 2, TimeUnit.SECONDS)// 设置超时时间
                        .readTimeout(60 * 2, TimeUnit.SECONDS)// 设置读取超时时间
                        .writeTimeout(60 * 2, TimeUnit.SECONDS)// 设置写入超时时间
                        .build();
            }
        }

        return okHttpsClient;
    }

    /**
     * 建立单例模式
     */
    protected static OkHttpClient getProxyHttpsInstance(String proxyHost, int proxyPort) {
        synchronized (HttpsClient.class) {
            if (okHttpsClientProxy == null) {
                // 这里是以毫秒为单位，1000 毫秒 = 1秒
                OkHttpClient.Builder builder = new OkHttpClient().newBuilder().proxySelector(new ProxySelector() {
                    @Override
                    public List<Proxy> select(URI uri) {
                        log.info("获取代理时,uri: " + uri);
                        List<Proxy> proxyList = new ArrayList<>(1);
                        proxyList.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
                        return proxyList;
                    }

                    @Override
                    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
                        log.info("获取代理失败时,uri: " + uri);
                        log.info("获取代理失败时,sa: " + sa);
                        log.info("获取代理失败时,ioe: " + ioe);
                    }
                });
                String tokenKey = getTokenKey();
                String tokenValue = (String) getTokenValue();
                if (StringUtils.isNotEmpty(tokenKey) && StringUtils.isNotEmpty(tokenValue)) {
                    builder = builder.addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request request = chain.request().newBuilder().addHeader(tokenKey, tokenValue).build();
                            return chain.proceed(request);
                        }
                    });
                }
                //base auth
                String userName = getUserName();
                String password = getPassword();
                if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
                    builder.authenticator((route, response) -> {
                        String credential = Credentials.basic(userName, password);
                        return response.request().newBuilder().header("Authorization", credential).build();
                    });
                }
                okHttpsClientProxy = builder.sslSocketFactory(createSSLSocketFactory()).hostnameVerifier(new HttpsClient.TrustAllHostnameVerifier()).connectTimeout(60 * 2, TimeUnit.SECONDS)// 设置超时时间
                        .readTimeout(60 * 2, TimeUnit.SECONDS)// 设置读取超时时间
                        .writeTimeout(60 * 2, TimeUnit.SECONDS)// 设置写入超时时间
                        .build();
            }
        }
        return okHttpsClientProxy;
    }

    /**
     * 重置OkHttpClient
     */
    public static void resetOkHttpClient() {
        synchronized (HttpsClient.class) {
            // 这里是以毫秒为单位，1000 毫秒 = 1秒
            OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
            String tokenKey = getTokenKey();
            String tokenValue = (String) getTokenValue();
            if (StringUtils.isNotEmpty(tokenKey) && StringUtils.isNotEmpty(tokenValue)) {
                builder = builder.addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        Request request = chain.request().newBuilder().addHeader(tokenKey, tokenValue).build();
                        return chain.proceed(request);
                    }
                });
            }

            //base auth
            String userName = getUserName();
            String password = getPassword();
            if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
                builder.authenticator((route, response) -> {
                    String credential = Credentials.basic(userName, password);
                    return response.request().newBuilder().header("Authorization", credential).build();
                });
            }

            okHttpClient = builder.connectTimeout(connectTimeout, TimeUnit.SECONDS)// 设置超时时间
                    .readTimeout(readTimeout, TimeUnit.SECONDS)// 设置读取超时时间
                    .writeTimeout(writeTimeout, TimeUnit.SECONDS)// 设置写入超时时间
                    .build();
        }
    }

    /**
     * @description createSSLSocketFactory @Author liangjl @Date 2018年2月9日
     * 下午3:15:47 @return 参数 @return SSLSocketFactory 返回类型 @throws
     */
    protected static SSLSocketFactory createSSLSocketFactory() {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new HttpsClient.TrustAllCerts()}, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }
        return ssfFactory;
    }

    @SneakyThrows
    protected static X509TrustManager createX509TrustManager() {
        TrustManagerFactory trustManagerFactory = null;
        trustManagerFactory = TrustManagerFactory.getInstance(
                TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init((KeyStore) null);
        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
        if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
            throw new IllegalStateException("Unexpected default trust managers:"
                    + Arrays.toString(trustManagers));
        }
        X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
        return trustManager;
    }


//
//    protected static String getToken() {
//        return token = token == null ? "" : token;
//    }
//
//    protected static void setToken(String token0) {
//        token = token0;
//    }

    /**
     * 获取tokenKey
     */
    public static String getTokenKey() {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        String tokenKey0 = (String) threadLocalMap.get(tokenKey);
        return tokenKey0;
    }

    public static void setTokenKey(String tokenKey0) {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        threadLocalMap.put(tokenKey, tokenKey0);
        threadLocal.set(threadLocalMap);
    }

    /**
     * 获取tokenValue
     */
    public static Object getTokenValue() {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        Object obj = threadLocalMap.get(tokenValue);
        return obj;
    }

    public static void setTokenValue(String tokenValue0) {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        threadLocalMap.put(tokenValue, tokenValue0);
        threadLocal.set(threadLocalMap);
    }

    /**
     * 获取userName
     */
    public static String getUserName() {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        String userName0 = (String) threadLocalMap.get(userName);
        return userName0;
    }

    public static void setUserName(String userName0) {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        threadLocalMap.put(userName, userName0);
        threadLocal.set(threadLocalMap);
    }

    /**
     * 获取password
     */
    public static String getPassword() {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        String password0 = (String) threadLocalMap.get(password);
        return password0;
    }

    public static void setPassword(String password0) {
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        threadLocalMap.put(password, password0);
        threadLocal.set(threadLocalMap);
    }

    /**
     * 初始化map
     *
     * @return
     */
    private static Map<String, Object> getThreadLocalMap() {
        Map<String, Object> map = threadLocal.get();
        if (map == null) {
            map = new HashMap<>();
            threadLocal.set(map);
        }
        return map;
    }
}
